#include "mesh.h"
#include <iostream>

Mesh::Mesh() {
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);
}

Mesh::~Mesh() {
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
}

void Mesh::update_buffer() {
    int n_vertices = vertices.size() / 3;
	int n_element = indices.size();
	std::vector<float> data(n_vertices * 5);
	for(int i = 0 ; i < n_vertices ; ++i) {
		data[5 * i] = vertices[3 * i];
		data[5 * i + 1] = vertices[3 * i + 1];
		data[5 * i + 2] = vertices[3 * i + 2];
		data[5 * i + 3] = textureCoord[2 * i];
		data[5 * i + 4] = textureCoord[2 * i + 1];
	}
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * n_vertices * 5, data.data(), GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * n_element, indices.data(), GL_STATIC_DRAW);

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

GLuint Mesh::get_VAO() const {
    return VAO;
}

void createCubeMesh(Mesh &mesh, const float size) {
    assert(size > 0);
	mesh.vertices.assign({
		-0.5f, -0.5f, -0.5f,
		0.5f, -0.5f, -0.5f, 
		0.5f,  0.5f, -0.5f, 
		0.5f,  0.5f, -0.5f, 
		-0.5f,  0.5f, -0.5f,
		-0.5f, -0.5f, -0.5f,

		-0.5f, -0.5f,  0.5f,
		0.5f, -0.5f,  0.5f, 
		0.5f,  0.5f,  0.5f, 
		0.5f,  0.5f,  0.5f, 
		-0.5f,  0.5f,  0.5f,
		-0.5f, -0.5f,  0.5f,

		-0.5f,  0.5f,  0.5f,
		-0.5f,  0.5f, -0.5f,
		-0.5f, -0.5f, -0.5f,
		-0.5f, -0.5f, -0.5f,
		-0.5f, -0.5f,  0.5f,
		-0.5f,  0.5f,  0.5f,

		0.5f,  0.5f,  0.5f, 
		0.5f,  0.5f, -0.5f, 
		0.5f, -0.5f, -0.5f, 
		0.5f, -0.5f, -0.5f, 
		0.5f, -0.5f,  0.5f, 
		0.5f,  0.5f,  0.5f, 

		-0.5f, -0.5f, -0.5f,
		0.5f, -0.5f, -0.5f, 
		0.5f, -0.5f,  0.5f, 
		0.5f, -0.5f,  0.5f, 
		-0.5f, -0.5f,  0.5f,
		-0.5f, -0.5f, -0.5f,
		
		-0.5f,  0.5f, -0.5f,
		0.5f,  0.5f, -0.5f, 
		0.5f,  0.5f,  0.5f, 
		0.5f,  0.5f,  0.5f, 
		-0.5f,  0.5f,  0.5f,
		-0.5f,  0.5f, -0.5f,});
	
	for(auto &x: mesh.vertices) x *= size;
	
	mesh.textureCoord.assign({
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		1.0f, 1.0f,
		0.0f, 1.0f,
		0.0f, 0.0f,
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		1.0f, 1.0f,
		0.0f, 1.0f,
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		0.0f, 1.0f,
		0.0f, 1.0f,
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		0.0f, 1.0f,
		0.0f, 1.0f,
		0.0f, 0.0f,
		1.0f, 0.0f,
		0.0f, 1.0f,
		1.0f, 1.0f,
		1.0f, 0.0f,
		1.0f, 0.0f,
		0.0f, 0.0f,
		0.0f, 1.0f,
		0.0f, 1.0f,
		1.0f, 1.0f,
		1.0f, 0.0f,
		1.0f, 0.0f,
		0.0f, 0.0f,
		0.0f, 1.0f,
	});

	mesh.indices.assign({
		0,	1,	2,
		3,	4,	5,
		6,	7,	8,
		9,	10,	11,
		12,	13,	14,
		15,	16,	17,
		18,	19,	20,
		21,	22,	23,
		24,	25,	26,
		27,	28,	29,
		30,	31,	32,
		33,	34,	35
	});
}

void createTriangleMesh(Mesh& mesh, glm::vec3 a, glm::vec3 b, glm::vec3 c) {
	mesh.vertices.assign({
		a.x,a.y,a.z,
		b.x,b.y,b.z,
		c.x,c.y,c.z,
		});
	mesh.textureCoord.assign({
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		});
	mesh.indices.assign({
		0,	1,	2,
		3,	4,	5,
		6,	7,	8,
		});
}

namespace ball_mesh {

constexpr float X = .525731112119133606f;
constexpr float Z = .850650808352039932f;

static glm::vec3 v[12] = {
    { -X, 0.0f,   Z }, {  X, 0.0f,   Z }, { -X, 0.0f,  -Z }, {  X, 0.0f,  -Z },
    {0.0f,   Z,   X }, {0.0f,   Z,  -X }, {0.0f,  -Z,   X }, {0.0f,  -Z,  -X },
    {  Z,   X, 0.0f }, { -Z,   X, 0.0f }, {  Z,  -X, 0.0f }, { -Z,  -X, 0.0f }
};

static GLuint tindices[20][3] = {
    {1, 4, 0},{ 4,9,0}, {4, 5,9}, {8,5, 4}, { 1,8,4},
    {1,10, 8},{10,3,8}, {8, 3,5}, {3,2, 5}, { 3,7,2},
    {3,10, 7},{10,6,7}, {6,11,7}, {6,0,11}, { 6,1,0},
    {10,1, 6},{11,0,9}, {2,11,9}, {5,2, 9}, {11,2,7}
};

void divide_triangle(Mesh& mesh, glm::vec3 a, glm::vec3 b, glm::vec3 c, int n) {
    glm::vec3 v1, v2, v3;
    if (n > 0) {
        // 如果使用归一化，顶点和的结果是否除以 2 都不影响结果
        v1 = (a + b);
        v2 = (a + c);
        v3 = (b + c);

        // 归一化 的坐标贴近球面。但如果不进行归一化，显示的就是一个细分的四面体
        v1 = glm::normalize(v1);
        v2 = glm::normalize(v2);
        v3 = glm::normalize(v3);

        divide_triangle(mesh, a, v2, v1, n - 1);
        divide_triangle(mesh, c, v3, v2, n - 1);
        divide_triangle(mesh, b, v1, v3, n - 1);
        divide_triangle(mesh, v1, v2, v3, n - 1);
        // 递归细分相邻的三角形
        divide_triangle(mesh, v1, b, v3, n - 1);
        divide_triangle(mesh, v2, c, v1, n - 1);
    }
    else
        triangle(mesh, a, b, c);
}

void triangle(Mesh& mesh, glm::vec3 a, glm::vec3 b, glm::vec3 c) {
	int VerticeNum = mesh.vertices.size();
    mesh.vertices.emplace_back(a[0]);
    mesh.vertices.emplace_back(a[1]);
    mesh.vertices.emplace_back(a[2]);
    mesh.vertices.emplace_back(b[0]);
    mesh.vertices.emplace_back(b[1]);
    mesh.vertices.emplace_back(b[2]);
    mesh.vertices.emplace_back(c[0]);
    mesh.vertices.emplace_back(c[1]);
    mesh.vertices.emplace_back(c[2]);
    // 计算纹理坐标
    glm::vec2 uv_a = sphericalToTextureCoord(a);
    glm::vec2 uv_b = sphericalToTextureCoord(b);
    glm::vec2 uv_c = sphericalToTextureCoord(c);
    mesh.textureCoord.emplace_back(uv_a[0] > 0.5 ? 1 - uv_a[0] : uv_a[0]);
    //mesh.textureCoord.emplace_back(uv_a[1] > 0.5 ? 1 - uv_a[1] : uv_a[1]);
    mesh.textureCoord.emplace_back(uv_a[1]);
    mesh.textureCoord.emplace_back(uv_b[0] > 0.5 ? 1 - uv_b[0] : uv_b[0]);
    //mesh.textureCoord.emplace_back(uv_b[1] > 0.5 ? 1 - uv_b[1] : uv_b[1]);
    mesh.textureCoord.emplace_back(uv_b[1]);
    mesh.textureCoord.emplace_back(uv_c[0] > 0.5 ? 1 - uv_c[0] : uv_c[0]);
    // this->_mesh.textureCoord.emplace_back(uv_c[1] > 0.5 ? 1 - uv_c[1] : uv_c[1]);
    mesh.textureCoord.emplace_back(uv_c[1]);
    for (int i = 0; i < 9; i++) {
        mesh.indices.emplace_back(VerticeNum+i);
    }
}

glm::vec2 sphericalToTextureCoord(glm::vec3 point) {
    float u = 0.5f + atan2(point.z, point.x) / (2.0f * glm::pi<float>());
    float v = 0.5f - asin(point.y) / glm::pi<float>();
    return glm::vec2(u, v);
}

}

void createIcosphereMesh(Mesh &mesh, int detail) {
	using namespace ball_mesh;
	for (int i = 0; i < 20; ++i) {
        divide_triangle(mesh, v[tindices[i][0]], v[tindices[i][1]], v[tindices[i][2]], detail);
    }
}